﻿// <copyright file="ModelVisual3D.cs" company="Axelerate Solutions S.A.">
// Copyright (c) Axelerate Solutions S.A.  All rights reserved.
// </copyright>

namespace Axelerate.Silverlight3D.Media.Media3D
{
    using System;
    using System.Net;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Documents;
    using System.Windows.Ink;
    using System.Windows.Input;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Shapes;
    using System.Windows.Markup;
    using System.Collections.Generic;
    using Axelerate.Silverlight3D.Controls;
    using System.Threading;
    using System.Diagnostics;

    /// <summary>
    /// File name: ModelVisual3D.cs
    /// Company: Axelerate Solutions S.A.
    /// Created By: Daniel Rojas
    /// Created Date: 10/13/2008 16:54:00 h.
    /// Description: Visual that contains 3-D models.
    ///   <Modifications>
    ///   </Modifications>
    /// </summary>
    public class ModelVisual3D : Visual3D
    {
        #region Fields

        /// <summary>
        /// Identifies the Content dependency property.
        /// </summary>
        public static readonly DependencyProperty ContentProperty =
                    DependencyProperty.Register(
                          "Content",
                          typeof(Model3D),
                          typeof(ModelVisual3D),
                          new PropertyMetadata(OnContentPropertyChanged));



        private List<RenderEntity> _catchedinnerRenderEntities;

        /// <summary>
        /// A list of all of the entities that make up all of the 
        /// models that are part of this visual.
        /// </summary>
        private Dictionary<Model3D, List<RenderEntity>> allModelRenderEntities = new Dictionary<Model3D, List<RenderEntity>>();

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the model that comprises the content of the ModelVisual3D. This is a dependency property.
        /// </summary>
        public Model3D Content
        {
            get
            {

                return (Model3D)this.GetValue(ContentProperty);
            }
            set
            {
                Model3D OldValue = (Model3D)this.GetValue(ContentProperty);
                if (OldValue != null)
                {
                    OldValue.PropertyChanged -= ContentValue_PropertyChanged;
                }
                this.SetValue(ContentProperty, value);
                value.SetParentWorldTransform(this, this.Transform.Value);
                value.PropertyChanged += ContentValue_PropertyChanged;
            }
        }

        /// <summary>
        /// Notify if the some property of the Content was changed
        /// </summary>
        /// <param name="sender">Object that send the event</param>
        /// <param name="e">event arguments</param>
        void ContentValue_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
        {
            this.CreateRenderEntities();
            this.NotifyPropertyChanged("Content");
        }

        /// <summary>
        /// Gets or set a value indicating wheter the model was rendered
        /// </summary>
        private bool modelWasRendered { get; set; }

        #endregion

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the ModelVisual3D class.
        /// </summary>
        public ModelVisual3D()
            : base()
        {
            this._catchedinnerRenderEntities = new List<RenderEntity>();
            this.RenderedModels = new List<Model3D>();
            this.Transform = new MatrixTransform3D();
        }
        #endregion

        #region Methods

        protected override void NotifyPropertyChanged(string propertyName)
        {
            base.NotifyPropertyChanged(propertyName);
            if (propertyName == "Transform" && Content != null)
            {
                Content.SetParentWorldTransform(this, GetLocalToWorldTransform());
            }
        }

        internal override void UpdateWorldTransform()
        {
            base.UpdateWorldTransform();
            if (Content != null)
            {
                Content.SetParentWorldTransform(this, GetLocalToWorldTransform());
            }
        }


        /// <summary>
        /// Raise the Content changed event
        /// </summary>
        /// <param name="d">DependencyObject owner of the property</param>
        /// <param name="e">DependencyPropertyChangedEventArgs with the necesary information</param>
        private static void OnContentPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ((ModelVisual3D)d).CreateRenderEntities();
            ((ModelVisual3D)d).NotifyPropertyChanged("Content");
        }

        #endregion

        /// <summary>
        /// Gets the inner list of the render entities
        /// </summary>
        internal override List<RenderEntity> innerRenderEntities
        {
            get
            {
                return this._catchedinnerRenderEntities;
            }
        }

        /////// <summary>
        /////// Called when the model is dirty to regenered the content render entities
        /////// </summary>
        /////// <param name="viewport">parent viewport that call the update view</param>
        /////// <param name="transformations">Matrix3D with the actual transformation</param>
        ////protected override void GenerateContent2DShapes(Viewport3D viewport, Matrix3D transformations)
        ////{
        ////    Content.Update2DShapes(viewport, transformations);
        ////    _catchedinnerRenderEntities.Clear();
        ////    _catchedinnerRenderEntities.AddRange(Content.innerRenderEntities);
        ////}

        internal override void CreateRenderEntities()
        {
            if ((this.Content == null) || (this.Viewport == null))
            {
                return;
            }

            this.Content.CreateRenderEntities(this.Viewport.RenderEntitiesPool);
            this.IsDirty = true;
        }

        private void FindLightModels(List<Light> lights, Model3D Content)
        {
            if (Content is Model3DGroup)
            {
                foreach (Model3D item in ((Model3DGroup)Content).Children)
                {
                    this.FindLightModels(lights, item);
                }
            }
            else if (Content is Light)
            {
                lights.Add((Light)Content);
            }
        }

        /// <summary>
        /// Inner class used to store all the common information need to do the render. (FOR FUTURE USE)
        /// </summary>
        internal class RenderContext
        {
            public ManualResetEvent _doneEvent;

            public Camera camera { get; set; }
            public Matrix3D worldToScreenTransform { get; set; }
            public List<Light> lights { get; set; }
            public Model3D model { get; set; }
            public List<RenderEntity> entitiesToRender { get; set; }


            /// <summary>
            /// Create a instance of the renderContext (FOR FUTURE USE)
            /// </summary>
            /// <param name="Camera">Pointer to the Camera</param>
            /// <param name="WorldToScreenMatrix">Matrix World to Screen precalculated</param>
            /// <param name="Lights">List of lights</param>
            /// <param name="Model">Model to be rendered</param>
            public RenderContext(Camera Camera, Matrix3D WorldToScreenMatrix, List<Light> Lights, Model3D Model, List<RenderEntity> EntitiesToRender, ManualResetEvent doneEvent)
            {
                this.camera = Camera;
                this.worldToScreenTransform = WorldToScreenMatrix;
                this.lights = Lights;
                this.model = Model;
                this.entitiesToRender = EntitiesToRender;
                this._doneEvent = doneEvent;
            }
        }

        internal override void Render(Viewport3D Viewport, List<RenderEntity> entitiesToRender, List<ModelVisual3D> renderedVisuals)
        {
            this.RenderedModels.Clear();
            this.modelWasRendered = false;

            if (this.Content != null)
            {

                // Render all of the relevant entities
                Camera camera = Viewport.Camera;
                Matrix3D worldToScreenMatrix = camera.WorldToScreenMatrix;

                // get the List of lights that appear on the scene (if apply)
                List<Light> lights = new List<Light>();
                if (Viewport.LightModel != LightModelEnum.None)
                {
                    foreach (ModelVisual3D mv in this.Viewport.Children)
                    {
                        this.FindLightModels(lights, mv.Content);
                    }
                }

                modelWasRendered = this.Content.Render(this, Viewport, entitiesToRender, worldToScreenMatrix, lights);
            }
            if (this.Children != null && this.Children.Count > 0)
            {
                this.Children.Render(Viewport, entitiesToRender, renderedVisuals);

            }
            
            //// Keep track of all of the visuals we rendered
            if (modelWasRendered)
            {
                renderedVisuals.Add(this);
            }

            this.IsDirty = false;
        }



        internal List<Model3D> RenderedModels
        {
            get;
            private set;
        }
    }
}
